特定の IAM ロールのみアクセスできる S3 バケットをaws:PrincipalArnを用いて実装した
はじめに
好物はインフラとフロントエンドのかじわらゆたかです。
特定のIAM Roleからのみアクセスを受け付けるS3バケットを書こうとする場合、 以下のブログを参考にIAM Roleに紐づいているaws:userIdを用いる方法を行っていましたが、aws:PrincipalArnを用いることで同様のことができました。
aws:PrincipalArn ってなに?
AWS グローバル条件コンテキストキーの一つです。 ドキュメントに以下のが記載されております。
このキーを使用して、リクエストを行ったプリンシパルの Amazon リソースネーム (ARN) をポリシーで指定したARN と比較します。IAM ロールの場合、リクエストコンテキストは、ロールを引き受けたユーザーの ARN ではなく、ロールの ARN を返します。
AWS グローバル条件コンテキストキー - AWS Identity and Access Management
確かにロールのARNを指定することが可能に見えるので、確認してみました。
検証用Cloudformation テンプレート
s3-bucket-acces-to-a-specific-role-use-principal-arn.yaml
上記のテンプレートを動かして検証しました。 上記のテンプレートを作成すると以下のリソースが作成されます。
- アクセスが許可されたIAM Role
- アクセスが許可されていないIAM Role
- アクセスが許可されたIAM Roleからアクセス可能な検証用のS3バケット
- SFTPプロトコルで待ち受けるTransferFamilyのサーバー
- アクセスが許可されたIAM Roleと紐づいているSFTPサーバーのユーザー
- アクセスが許可されていないIAM Roleと紐づいているSFTPサーバーのユーザー
- アクセスが許可されたIAM Roleと紐づいているS3にアクセスるするためのLambda
- アクセスが許可されていないIAM Roleと紐づいているS3にアクセスるするためのLambda
S3のBucketPolicyは以下のようになっています
{ "Version": "2012-10-17", "Statement": [ { "Sid": "MultiRestrictPolicy", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::{検証用のS3バケット}", "arn:aws:s3:::{検証用のS3バケット}/*" ], "Condition": { "StringNotEquals": { "aws:PrincipalArn": "{アクセスが許可されたIAM RoleのARN}", "aws:CalledVia": "cloudformation.amazonaws.com" } } } ] }
なお、SFTPユーザー用の公開鍵は作成してパラメータストアに登録しておく必要があります。 公開鍵の作成と登録は以下のようなシェルを作りました。
ssh-keygen -t rsa -b 4096 -C "s3-access-check-key" -f ./s3-access-check-key aws ssm put-parameter \ --name s3-access-check-key \ --value \"$(cat ./s3-access-check-key.pub)\" \ --type String
検証してみた
IAM Roleでアクセスしてみた
以下のようなaws/configを作成し、Assume元をIAM User/IAM Roleで確認しました。
[profile check-access-allow-from-role] output = json role_arn = {アクセスが許可されたIAM Role} source_profile = {Assume元のIAM Role} region = ap-northeast-1 [profile check-access-allow-from-user] output = json role_arn = {アクセスが許可されたIAM Role} source_profile = {Assume元のIAM User} region = ap-northeast-1 [profile check-access-deny-from-role] output = json role_arn = {アクセスが許可されてないIAM Role} source_profile = {Assume元のIAM Role} region = ap-northeast-1 [profile check-access-deny-from-user] output = json role_arn = {アクセスが許可されていないIAM Role} source_profile = {Assume元のIAM User} region = ap-northeast-1
想定通りアクセス用のIAM Roleの権限でアクセス制御ができています。
$ aws s3 cp ./Happy20thanniversarytoClassMethod.txt s3://cm-kajiwara-access-check --profile check-access-allow-from-role upload: ./Happy20thanniversarytoClassMethod.txt to s3://cm-kajiwara-access-check/Happy20thanniversarytoClassMethod.txt $ aws s3 ls cm-kajiwara-access-check --profile check-access-allow-from-role 2023-07-07 14:20:51 0 Happy20thanniversarytoClassMethod.txt $ aws s3 ls cm-kajiwara-access-check --profile check-access-allow-from-user 2023-07-07 14:20:51 0 Happy20thanniversarytoClassMethod.txt $ aws s3 ls cm-kajiwara-access-check --profile check-access-deny-from-user An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied $ aws s3 ls cm-kajiwara-access-check --profile check-access-deny-from-role An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
アクセスが許可されたIAM RoleにAssume Roleをした後にアクセスができていることと、 アクセスが許可されてないIAM RoleにAssume Roleをしたあとにアクセスができていないことが確認できました。
このような形でSession名を変更しても問題ありません
[profile check-access-allow-from-role-chenge-session-name] role_arn = {アクセスが許可されたIAM Role} role_session_name = anniversary_to_classmethod source_profile = {Assume元のIAM Role}
$ aws s3 ls cm-kajiwara-access-check --profile check-access-allow-from-role-chenge-session-name 2023-07-07 14:20:51 0 Happy20thanniversarytoClassMethod.txt
Lambdaでアクセスしてみた
実装したLambdaを実行してみました こちらも割り当てた権限通りの動きをしています。
$ aws lambda invoke --function-name {アクセスが許可されたIAM Roleと紐づいているS3にアクセスるするためのLambda} out --log-type Tail --query 'LogResult' --output text | base64 -d START RequestId: dd5bfc27-f6d1-49e1-9c0e-8d009bcbfbd4 Version: $LATEST END RequestId: dd5bfc27-f6d1-49e1-9c0e-8d009bcbfbd4 REPORT RequestId: dd5bfc27-f6d1-49e1-9c0e-8d009bcbfbd4 Duration: 2426.35 ms Billed Duration: 2427 ms Memory Size: 128 MB Max Memory Used: 77 MB Init Duration: 272.55 ms
$ aws lambda invoke --function-name {アクセスが許可されてないIAM Roleと紐づいているS3にアクセスるするためのLambda} out --log-type Tail --query 'LogResult' --output text | base64 -d START RequestId: 94804667-3dd5-4988-8bff-3e54867e9ad4 Version: $LATEST [ERROR] ClientError: An error occurred (403) when calling the HeadObject operation: Forbidden Traceback (most recent call last): File "/var/task/index.py", line 6, in lambda_handler s3.download_file("cm-kajiwara-access-check", 'Happy20thanniversarytoClassMethod.txt', '/tmp/Happy20thanniversarytoClassMethod.txt') (中略) REPORT RequestId: 94804667-3dd5-4988-8bff-3e54867e9ad4 Duration: 338.34 ms Billed Duration: 339 ms Memory Size: 128 MB Max Memory Used: 79 MB
TransferFamilyでアクセスしてみた
こちらも権限の想定通りの動きをしています
$ sftp -i ./s3-access-check-key {アクセスが許可されたIAM Roleと紐づいているSFTPサーバーのユーザー}@{SFTPプロトコルで待ち受けるTransferFamilyのサーバー} Connected to {SFTPプロトコルで待ち受けるTransferFamilyのサーバー}. sftp> cd cm-kajiwara-access-check sftp> ls Happy20thanniversarytoClassMethod.txt
$ sftp -i ./s3-access-check-key {アクセスが許可されてないIAM Roleと紐づいているSFTPサーバーのユーザー}@{SFTPプロトコルで待ち受けるTransferFamilyのサーバー} Connected to {SFTPプロトコルで待ち受けるTransferFamilyのサーバー}. sftp> cd cm-kajiwara-access-check sftp> ls Couldn't read directory: Permission denied
まとめ
特定のIAM RoleからのみアクセスできるS3バケットとそのバケットポリシーをaws:PrincipalArnを用いる形で記載することができました。
今回様々なケースで検証しましたが、どのケースでも想定通りの動きをしてくれました。 この方法だとバケットポリシーを見直したときも、 aws:userIdを用いていたときに比べIAM RoleのARNが確認できることになります。
特定のIAM Roleからのみアクセスしたいと言ったとき、この方法を採用していただければと思います。
補足
BucketPolicyは非常に強い制約となり、間違えて記載するとAdministrator Policyが割当たっているIAMであっても書き換えが行えなくなります。 ですので、直接書き換えるのではなくCloudformationから記載をすることをおすすめいたします。 こちらの詳細については以下のブログが参考になります。